package com.gitee.wsl.ext.map


/**
 * Swaps dimensions in two-dimensional map. The returned map has keys from the second dimension as primary keys
 * and primary keys are used in the second dimension.
 */
fun <K1, K2, V> Map<K1, Map<K2, V>>.swapKeys(): Map<K2, Map<K1, V>> = swapKeysTo(LinkedHashMap()) { LinkedHashMap<K1, V>() }

/**
 * Swaps dimensions in two-dimensional map. The returned map has keys from the second dimension as primary keys
 * and primary keys are stored in the second dimension. [topDestination] specifies which map should be used to store the new primary keys
 * and [bottomDestination] is used to store the new secondary keys.
 */
inline fun <K1, K2, V, M2 : MutableMap<K1, V>, M1 : MutableMap<K2, M2>> Map<K1, Map<K2, V>>.swapKeysTo(
    topDestination: M1,
    bottomDestination: () -> M2
): M1 {
    this.forEach { (key1, map) ->
        map.forEach { (key2, value) ->
            topDestination.getOrPut(key2, bottomDestination)[key1] = value
        }
    }
    return topDestination
}

/**
 * Works similarly as [swapKeys] but the final map has three levels.
 * [transform] specifies how the keys should be swapped (or modified) in the newly created
 * map.
 */
inline fun <K1, K2, K3, KR1, KR2, KR3, V> Map<K1, Map<K2, Map<K3, V>>>.swapKeys(transform: (K1, K2, K3) -> Triple<KR1, KR2, KR3>):
        Map<KR1, Map<KR2, Map<KR3, V>>> =
    this.swapKeysTo(LinkedHashMap(), { LinkedHashMap<KR2, MutableMap<KR3, V>>() }, { LinkedHashMap() }, transform)

/**
 * Works similarly as [swapKeys] but the final map has three levels.
 * [transform] specifies how the keys should be swapped (or modified) in the newly created
 * map. [topDestination] specifies which map should be used to store the new primary keys,
 * [middleDestination] is used to store the new secondary keys and
 * [bottomDestination] is used to store the new tertiary keys.
 */
inline fun <K1, K2, K3, KR1, KR2, KR3, V, M3 : MutableMap<KR3, V>, M2 : MutableMap<KR2, M3>, M1 : MutableMap<KR1, M2>>
        Map<K1, Map<K2, Map<K3, V>>>.swapKeysTo(
    topDestination: M1,
    middleDestination: () -> M2,
    bottomDestination: () -> M3,
    transform: (K1, K2, K3) -> Triple<KR1, KR2, KR3>
): M1 {

    this.forEach { (key1, map2) ->
        map2.forEach { (key2, map3) ->
            map3.forEach { (key3, value) ->
                val (kr1, kr2, kr3) = transform(key1, key2, key3)
                topDestination.getOrPut(kr1, middleDestination).getOrPut(kr2, bottomDestination)[kr3] = value
            }
        }
    }

    return topDestination
}